package com.ifp.opengate.boot.filter;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.ifp.adapter.common.AdapterCanstants;
import com.ifp.core.context.ClogicContext;
import com.ifp.core.log.Trace;
import com.ifp.opengate.boot.constants.Constants;
import com.ifp.opengate.boot.emum.ErrorEnum;
import com.ifp.opengate.boot.exception.AccessControlException;
import com.ifp.opengate.boot.utils.Md5Utils;
import com.ifp.opengate.boot.utils.RedisUtils;

import reactor.core.publisher.Mono;

/**
 * @author liwq
 * @description 鉴权(获取访问令牌 + 渠道访问鉴权)
 * @date 2021/8/12 14:57
 */
@Component
public class OAuthGateWayGlobalFilter implements GlobalFilter, Ordered {

    /**
     * 请求获取Token 的路径
     */
    private static final String GET_TOKEN_URL = "/query/gateway.getToken";
    /**
     * Token生效时间，8小时
     */
    private static final Long EFFECT_SECONDS = 8 * 60 * 60L;

    /**
     * channel、appid、appsecret对应关系
     */
    private static final String CHANNEL_AUTHENTICATION_ACCOUNT = "opengate_channel_authentication_account";

    /**
     * Request Token Redis键前缀
     */
    private static final String GATEWAY_TOKEN_KEY_PREFIX = "opengate:accessToken:";

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain chain) {
        Trace.logInfo(Trace.MODULE_FILTER, "OAuthGlobalFilterTemp start filter....");
        ClogicContext clogicContext = (ClogicContext)serverWebExchange.getAttributes().get(Constants.CL_CONTEXT);
        String transCode = clogicContext.getTransCode();
        Map<String, Object> dataMap = clogicContext.getDataMap();
        // 请求报文头
        Map<String, Object> headerMap = (Map<String, Object>)dataMap.get(AdapterCanstants.HEADER);
        String appId = (String)headerMap.get("appId");
        String appSecret = (String)headerMap.get("appSecret");
        String channel = (String)headerMap.get("channel");
        String accessToken = (String)headerMap.get("accessToken");
        String version = (String)headerMap.get("version");
        String userId = (String)headerMap.get("userId");
        String requestId = (String)headerMap.get("requestId");

        // Step1:校验当前上送的channel,appId,appSecret合法性
        // step1.1: 上送参数为空校验
        if (!GET_TOKEN_URL.equals(transCode)) {
            if (StringUtils.isEmpty(accessToken)) {
                throw new AccessControlException(ErrorEnum.oauthAccessTokenIsNullError.code(),
                    ErrorEnum.oauthAccessTokenIsNullError.msg());
            }
        }
        if (!GET_TOKEN_URL.equals(transCode)) {
            if (StringUtils.isEmpty(userId)) {
                throw new AccessControlException(ErrorEnum.oauthUserIdIsNullError.code(),
                    ErrorEnum.oauthUserIdIsNullError.msg());
            }
        }
        if (!GET_TOKEN_URL.equals(transCode)) {
            if (StringUtils.isEmpty(requestId)) {
                throw new AccessControlException(ErrorEnum.oauthRequestIdIsNullError.code(),
                    ErrorEnum.oauthRequestIdIsNullError.msg());
            }
        }
        if (StringUtils.isEmpty(version)) {
            throw new AccessControlException(ErrorEnum.oauthVersionIsNullError.code(),
                ErrorEnum.oauthVersionIsNullError.msg());
        }
        if (StringUtils.isEmpty(appId) || StringUtils.isEmpty(appSecret) || StringUtils.isEmpty(channel)) {
            throw new AccessControlException(ErrorEnum.oauthAppIdOrSecretIsNullError.code(),
                ErrorEnum.oauthAppIdOrSecretIsNullError.msg());
        }
        // step1.2: 检查是否在内管配置了
        List<String> authRelations = redisUtils.lGet(CHANNEL_AUTHENTICATION_ACCOUNT, 0, -1);
        String authKey = channel + appId + appSecret;
        if (authRelations == null || authRelations.size() == 0) {
            throw new AccessControlException(ErrorEnum.oauthAppIdAndSecretNoRelationError.code(),
                ErrorEnum.oauthAppIdAndSecretNoRelationError.msg());
        }
        if (authRelations != null && authRelations.size() > 0) {
            if (!authRelations.contains(authKey)) {
                throw new AccessControlException(ErrorEnum.oauthAppIdAndSecretNoRelationError.code(),
                    ErrorEnum.oauthAppIdAndSecretNoRelationError.msg());
            }
        }

        // Step2: 判断是不是获取Token 请求
        // Step2.1: 若是获取Token的请求，则直接返回Token
        String token = null;
        String tokenKey = GATEWAY_TOKEN_KEY_PREFIX + channel + "_" + appId + "_" + appSecret;
        if (GET_TOKEN_URL.equals(transCode)) {
            token = (String)redisUtils.get(tokenKey);
            if (StringUtils.isEmpty(token)) {
                token = genericToken(appId, appSecret);
                redisUtils.set(tokenKey, token, EFFECT_SECONDS);
            }
            ServerHttpResponse httpResponse = serverWebExchange.getResponse();
            httpResponse.setStatusCode(HttpStatus.OK);
            Map<String, Object> dataMaps = new HashMap<>(4);
            dataMaps.put("token", token);
            DataBuffer buffer = httpResponse.bufferFactory()
                .wrap(JSONObject.wrap(dataMaps).toString().getBytes(StandardCharsets.UTF_8));
            return httpResponse.writeWith(Mono.just(buffer));

        }
        // Step2.2: 若不是获取Token的请求,则校验Token合法性
        // Step2.2.1: 是否过期失效
        token = (String)redisUtils.get(tokenKey);
        if (StringUtils.isEmpty(token)) {
            throw new AccessControlException(ErrorEnum.oauthLoseEfficacyError.code(),
                ErrorEnum.oauthLoseEfficacyError.msg());
        }
        // Step2.2.2: Token是否正确
        if (!accessToken.equals(token)) {
            throw new AccessControlException(ErrorEnum.oauthWrongTokenError.code(),
                ErrorEnum.oauthWrongTokenError.msg());
        }

        return chain.filter(serverWebExchange);
    }

    @Override
    public int getOrder() {
        return -13;
    }

    /**
     * 生成Token
     * 
     * @param appId
     * @param appSecret
     * @return
     */
    public String genericToken(String appId, String appSecret) {
        StringBuffer sb = new StringBuffer();
        sb.append(appId).append(appSecret);
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            sb.append(random.nextInt(10));
        }
        String randomStr = sb.toString();
        String md5Hex = Md5Utils.getMd5Hex(randomStr);
        return md5Hex;
    }

}
